package de.uniulm.muc.mensafinder;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
/*
import org.openintents.sensorsimulator.hardware.SensorManagerSimulator;
import org.openintents.sensorsimulator.hardware.Sensor;
import org.openintents.sensorsimulator.hardware.SensorEvent;
import org.openintents.sensorsimulator.hardware.SensorEventListener;
*/
import android.hardware.SensorManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

/**
 * When the phone is oriented towards the staircase, the user can start the discovery mode. 
 * The application then sends the current compass direction and username to the Mensafinder server
 * 
 * @author Adam Just
 */
public class DiscoveryActivity extends Activity implements SensorEventListener {
	
	//SensorManagerSimulator sMgr = null; //TODO: comment out
	SensorManager sMgr; 
	
	// different send options
	private final int SEND_DATA = 0;
	private final int SEND_UNDISCOVERABLE = 1;
	

	float azimuth, pitch, roll = 0.0f;  // variables for orientation - azimuth includes 360 degrees
	float x, y, z = 0.0f;		     // variables for acceleration	
	float lastX, lastY, lastZ = 0.0f;
	float deltaX, deltaY, deltaZ = 0.0f;
	
	boolean initialized = false;
	boolean showMessage = false;
	
	
	TextView tv;
	Button discoverableBtn;
	boolean discoverable = false;	// phone is not discoverable by default
	
	private SettingsDbAdapter dbAdapter; // saving the username in a sqlite database
	
	private Timer timer; // to update orientation every 4 minutes, because within 5 minutes orientation will expire
	
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.discovery);
        
        tv = (TextView) this.findViewById(R.id.textView2);
        discoverableBtn = (Button) this.findViewById(R.id.discoverableButton);
        
        //sMgr = SensorManagerSimulator.getSystemService(this, SENSOR_SERVICE); //TODO: comment out
        //sMgr.connectSimulator(); //TODO: comment out
        sMgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);
        
        dbAdapter = new SettingsDbAdapter(this);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        sMgr.registerListener(this, sMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_FASTEST);
        sMgr.registerListener(this, sMgr.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_FASTEST);
    }
    
    @Override
    protected void onPause() {
        sMgr.unregisterListener(this);
        super.onPause();
    }
    
    /**
     * Method sends data to server if discoverable button is pressed
     * @param v is the view
     */
    public void discoverable(View v) {
    	if (!discoverable) {
    		sendData(SEND_DATA);
    	} else {
    		sendData(SEND_UNDISCOVERABLE);
    	}
    }
    
    /**
     * Sends the username and orientation to server or deletes the user.
     * @param id to identify the right operation
     */
    private void sendData(int id) {
    	switch (id) {

    		case SEND_DATA:
	    		if (checkOrientation()) {
	    			Log.i("MensaFinder", "Send username, status and orientation");
	    			
	    			if (putRequest()) {
	    				if (!discoverable) {
	    					switchStatus();
	    				}
	    				
	    				if (timer != null) {
	    					timer.cancel();
	    				}
	    					timer = new Timer();	// update orientation every 4 minutes
	    					timer.schedule(new TimerTask() {
	    						@Override
	    						public void run() {
	    							if(!putRequest()) {
	    								Log.i("MensaFinder", "Orientation update failed");
	    								Handler refresh = new Handler(Looper.getMainLooper());
	    								refresh.post(new Runnable() {
	    								    public void run()
	    								    {
	    								        updateFailed();
	    								    }
	    								});
	    							}
	    						}
	    					}, 240000, 240000);
	    			} else {
	    				alertDialog("Discoverable failed!", "User not found or connection failed.");
	    			}
	    		}
	    		break;
	    	
    		case SEND_UNDISCOVERABLE:
    			switchStatus();
    			timer.cancel();
    			
	    		if (deleteRequest()) {
	    			Log.i("MensaFinder", "Changed status to undiscoverable");
	    		} else {
	    			alertDialog("Undiscoverable failed!", "User not found or connection aborted.");
	    		}
	    		break;
    	}
    }
    
    /**
     * Shows an alert message if orientation update failed
     */
    private void updateFailed() {
    	timer.cancel();
    	switchStatus();
    	alertDialog("Orientation update failed!", "Maybe connection aborted");
    }    
    
    /**
     * Sends a PUT request to the mensafinder server with the specific URI
     * http://barracuda-vm8.informatik.uni-ulm.de/user/{name}/orientation/{degree}.
     * This request is necessary to register a new user and set its status to discoverable
     * 
     * @return true, if data was send
     */
    private boolean putRequest() {
		String username = dbAdapter.getUsername();
		int degree = (int) azimuth;
	
		String uri = "http://barracuda-vm8.informatik.uni-ulm.de/user/" + username + "/orientation/" + degree;

		int timeoutConnection = 6000;
		int timeoutSocket = 6000;
		HttpParams httpParameters = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
		HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
		
		HttpClient client = new DefaultHttpClient(httpParameters);
		HttpPut request = new HttpPut(uri);
		
		try {
			HttpResponse response = client.execute(request);
			HttpEntity entity = response.getEntity();
			System.out.println(entity.getContentType().getValue());
			
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_ACCEPTED) {
				Log.i("MensaFinder", "Orientation has been set");
				return true;
			} else {
				Log.i("MensaFinder", "PUT Request failed!");
				return false;
			}
		} catch (ConnectTimeoutException e) {
			e.printStackTrace();
			return false;
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
	}

        
    /**
     * Creates and showes a new alert dialog
     * @param title of the alert dialog
     * @param message to show
     */
    private void alertDialog(String title, String message) {
    	AlertDialog alert = new AlertDialog.Builder(this).create();
    	alert.setTitle(title);
    	alert.setMessage(message);
    	alert.setButton("OK", new OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {               
                    dialog.dismiss();
            }
        });
    	alert.show();
    }
    
    /**
     * Sends a DELETE request to the mensafinder server with the specific URI
     * http://barracuda-vm8.informatik.uni-ulm.de/user/{name}/orientation.
     * This request is necessary to delete a user (set his status to undiscoverable!)
     * 
     * @return true, if user has been removed
     */
    private boolean deleteRequest() {
    	String username = dbAdapter.getUsername();
	
		String uri = "http://barracuda-vm8.informatik.uni-ulm.de/user/" + username + "/orientation";
		
		int timeoutConnection = 6000;
		int timeoutSocket = 6000;
		HttpParams httpParameters = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
		HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
		
		HttpClient client = new DefaultHttpClient(httpParameters);
		HttpDelete request = new HttpDelete(uri);
		
		try {
			HttpResponse response = client.execute(request);
			
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) {
				Log.i("MensaFinder", "Orientation has been removed");
				return true;
			} else {
				Log.i("MensaFinder", "DELETE Request failed!");
				return false;
			}
		} catch (ConnectTimeoutException e) {
			e.printStackTrace();
			return false;
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
    }
    
    /**
     * Switches the status of a user from discoverable to undiscoverable or contrary
     */
	private void switchStatus() {
    	if (discoverable) {
    		discoverable = false;
    		tv.setText("Status: Not discoverable");
        	discoverableBtn.setText("discoverable");
    	} else {
    		discoverable = true;
    		tv.setText("Status: Discoverable");
        	discoverableBtn.setText("Undiscoverable");
    	}
    }
    
	
	/**
	 * Checks if the phone lies flat on a table.
	 * pitch and roll are 0.0f
	 * @return true, if phone lies flat on a table
	 */
    private boolean checkOrientation() {
    	Log.i("MensaFinder", "Orientation: " + azimuth + ", " + pitch + ", " + roll );
        Log.i("MensaFinder", "Accelerometer: " + x + ", " + y + ", " + z);
        
    	boolean isRest = (pitch > -5.0f && pitch < 5.0f) && (roll > -5.0f && roll < 5.0f);
        
        if (isRest) { // phone lies flat on a table
        	return true;
        } else {
        	alertDialog("Discoverable failed!", "Please put the phone flat on a table towards the staircase");
        	return false;
        }
    }

    /**
     * Finishes the current activity and returns to the main activity
     * @param v is the view
     */
    public void returnToMain(View v) {
    	Log.i("MensaFinder", "Return to main");
        if(discoverable) {
        	sendData(SEND_UNDISCOVERABLE);
        }
        this.setResult(0);
        this.finish();
    }

    /**
     * Shows an alert message if phone is moved during user is discoverable.
     * The user have the options to recalibrate the phone or to change his status to
     * undiscoverable.
     */
    private void phoneIsMoved() {
    	AlertDialog movedAlert = new AlertDialog.Builder(this).create();
    	movedAlert.setTitle("Phone is moved!");
    	movedAlert.setMessage("Do you want to recalibrate the device or set status to not discoverable?");
    	
    	movedAlert.setButton( "Recalibrate", new OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
            	sendData(SEND_DATA);
            	dialog.dismiss();
            	showMessage = false;
            }
        });
    	
    	movedAlert.setButton2("Cancel", new OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
            	dialog.dismiss();
            	showMessage = false;
            }
        });
    	
    	movedAlert.setButton3("Change status", new OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
            	sendData(SEND_UNDISCOVERABLE);
            	dialog.dismiss();
            	showMessage = false;
            }
        });
    	
        movedAlert.show();
    }
    
    
    /**
     * Detects the phone's orientation and acceleration
     */
	public void onSensorChanged(SensorEvent event) {
		if (event.sensor == sMgr.getDefaultSensor(Sensor.TYPE_ORIENTATION)) {
			azimuth = event.values[0];	// 0 = North, 90 = East, 180 = South, 270 = West
			pitch = event.values[1];
			roll = event.values[2];
		} else if (event.sensor == sMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)) {
			x = event.values[0];
			y = event.values[1];
			z = event.values[2];
			
			if (!initialized) {
				lastX = x;
				lastY = y;
				lastZ = z;
				initialized = true;
			} else {	// detect the direction of the acceleration
				deltaX = Math.abs(lastX - x);
				deltaY = Math.abs(lastY - y);
				deltaZ = Math.abs(lastZ - z);
				
				if (deltaX > 0.5f || deltaY > 0.5f || deltaZ > 0.5f) {
					//System.out.println("deltaX: " + deltaX + "   deltaY: " + deltaY + "   deltaZ: " + deltaZ);
					if (discoverable && !showMessage) {
						phoneIsMoved();
						showMessage = true;
					}
				}
				lastX = x;
				lastY = y;
				lastZ = z;
			}
		}
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}
    
}